#include "LogoWnd.h"
#include "FileUpdatedDlg.h"
#include "edit/EditConfigDlg.h"
#include "ExecStub.h"
#include "logolib/logo_lib.h"
#include <process.h>

HANDLE ghThreadMsgQReady = NULL;

CLogoWnd::CLogoWnd()
{
   m_hMenu = NULL;
   m_hIcon = NULL;
   m_exeInfo.hexe  = create_exe_handle();
   m_fn[0] = '\0';
   GetCurrentDirectory(_MAX_PATH, m_szConfigDir);
   m_bRunning = false;
   m_bDebugCmdAcked = true;
   m_right.m_container.m_canvas.SetExecCommander(this);
   m_pBkPtMgr = new CLogoBkPtMgr;
   m_pBkPtMgr->m_hexe = &(m_exeInfo.hexe);
   m_edit.SetBkPtMgr(m_pBkPtMgr);
   m_edit.SetDebuggerIF(&(m_right.m_info));
}

CLogoWnd::~CLogoWnd()
{
   if (m_exeInfo.hexe) {
      delete_exe_handle (m_exeInfo.hexe);
   }
   if (m_pBkPtMgr) {
      delete (m_pBkPtMgr);
   }
}

HWND CLogoWnd::CreateLogoWnd(RECT* pRect)
{
   DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
   DWORD dwStyle =  WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
   m_hMenu = ::LoadMenu(_Module.GetModuleInstance(), MAKEINTRESOURCE(IDR_MAINMENU));

   m_hWnd = Create (GetDesktopWindow(), *pRect, WINDOW_NAME_STR, dwStyle, dwExStyle, (UINT)m_hMenu);

   if (!m_hWnd) {
      DWORD err = GetLastError();
      return NULL;
   }
    
   ::ShowWindow(m_hWnd, SW_SHOW);
   ::UpdateWindow(m_hWnd);
   return m_hWnd;
}

LRESULT CLogoWnd::OnExit(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
   SetFocus();
   ::PostMessage (m_hWnd, WM_CLOSE, 0, 0);
   return 0;
}

LRESULT CLogoWnd::OnFileOpen(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
   LRESULT ret;
   char fn[_MAX_PATH];

   SetFocus();

   OPENFILENAME ofn;       // common dialog box structure
   // Initialize OPENFILENAME
   ZeroMemory(&ofn, sizeof(OPENFILENAME));
   ofn.lStructSize = sizeof(OPENFILENAME);
   ofn.hwndOwner = m_hWnd;
   ofn.lpstrFile = fn;
   fn[0] = '\0';
   ofn.nMaxFile = _MAX_PATH;
   ofn.lpstrFilter = "Logo Source File\0*.logo\0";
   ofn.nFilterIndex = 1;
   ofn.lpstrFileTitle = NULL;
   ofn.nMaxFileTitle = 0;
   ofn.lpstrInitialDir = NULL;
   ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;

   if (GetOpenFileName(&ofn)!=TRUE) {
      return 0;
   }

   ret = OnFileClose(wNotifyCode, wID, hWndCtl, bHandled);
   if (ret == IDCANCEL) {
      return 0;
   }
   assert (m_exeInfo.hexe); 
   ReadFile (fn);
   return 0;
}

void CLogoWnd::ReadFile(LPCTSTR lpfn)
{
   m_edit.ReadFromFile(lpfn);
   strcpy(m_fn, lpfn);
}

LRESULT CLogoWnd::OnFileClose(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
   SetFocus();

   if (m_bRunning) {
      ::MessageBox(m_hWnd, "Please first abort execution or stop debugging.", "Cannot close the file", MB_ICONEXCLAMATION|MB_OK);
      m_edit.SetFocus();
      return IDCANCEL;
   }

   if (m_edit.IsDirty()) {
       CFileUpdatedDlg dlg;
       int ret = dlg.DoModal();
       switch (ret) {
       case ID_YES:
          OnFileSave(wNotifyCode, wID, hWndCtl, bHandled);
          break;
       case ID_NO:
          break;
       case IDCANCEL:
          m_edit.SetFocus();
          return IDCANCEL;
       }
   }
   m_edit.CloseFile();
    
   if (m_exeInfo.hexe) {
      delete_exe_handle (m_exeInfo.hexe);
      m_exeInfo.hexe = NULL;
   }
   m_exeInfo.hexe = create_exe_handle();
   m_fn[0] = '\0';
   m_edit.SetFocus();
   return 0;
}

LRESULT CLogoWnd::OnFileSave(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
   SetFocus();
   if (m_fn[0] == '\0') {
      OnFileSaveAs(wNotifyCode, wID, hWndCtl, bHandled);
      return 0;
   }
   m_edit.SaveToFile(m_fn);
   m_edit.SetDirty(false);
   return 0;
}

LRESULT CLogoWnd::OnFileSaveAs(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
   SetFocus();
   OPENFILENAME ofn;       // common dialog box structure
   // Initialize OPENFILENAME
   m_fn[0] = '\0';
   ZeroMemory(&ofn, sizeof(OPENFILENAME));
   ofn.lStructSize = sizeof(OPENFILENAME);
   ofn.hwndOwner = m_hWnd;
   ofn.lpstrFile = m_fn;
   ofn.nMaxFile = _MAX_PATH;
   ofn.lpstrFilter = "Logo Source File\0*.logo\0";
   ofn.nFilterIndex = 1;
   ofn.lpstrFileTitle = NULL;
   ofn.nMaxFileTitle = 0;
   ofn.lpstrInitialDir = NULL;
   ofn.lpstrDefExt = "logo";
   ofn.Flags = 0;

   if (GetSaveFileName(&ofn)!=TRUE) {
      return 0;
   }
   m_edit.SaveToFile(m_fn);
   m_edit.SetDirty(false);
   return 0;
}

LRESULT CLogoWnd::OnExecRun(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
   SetFocus();
   if (m_bRunning) {
      return 0;
   }

   RunIt();
   return 0;
}

LRESULT CLogoWnd::RunIt(bool debug/*=false*/)
{
   assert (!m_bRunning);
   assert (m_exeInfo.hexe);

   m_bRunning = true;

   m_right.ClearAllInfo();
   bool dirty = m_edit.IsDirty();
   m_edit.SaveToFile(LOGO_TMP_FILE);
   m_exeInfo.pFilename = LOGO_TMP_FILE;
   clear_for_rerun (m_exeInfo.hexe);
   user_abort(m_exeInfo.hexe, false);
   set_debug_mode (m_exeInfo.hexe, debug);

   m_exeInfo.hCanvas = m_right.m_container.m_canvas.m_hWnd;
   if (ghThreadMsgQReady == NULL) {
      ghThreadMsgQReady = CreateEvent(NULL, FALSE, FALSE, NULL);
   }
   _beginthread(ExecThread, 0, &m_exeInfo);
   WaitForSingleObject(ghThreadMsgQReady, INFINITE);
   EnableExecutionMenu();
   return 0;
}

void CLogoWnd::ExecutionDone (void)
{
   m_right.m_info.Enable(false);
   m_bRunning = false;
   m_edit.SetDebugLineNo(LAST_LINE);
   assert (m_exeInfo.hexe);

   clear_for_rerun(m_exeInfo.hexe);
   EnableExecutionMenu();
}

void CLogoWnd::DebugCommandFinished(unsigned int lineno)
{
   m_bDebugCmdAcked = true;
   lineno --;
   m_edit.SetDebugLineNo(lineno);
   EnableExecutionMenu();
   m_right.m_info.Enable();
   m_right.m_info.ClearAllInfo();
   m_right.m_info.GetStackInfo(m_exeInfo.hexe);
   return;
}

void CLogoWnd::CheckRunAndAbortMenus ()
{
   if (m_bRunning) {
      EnableMenuItem(m_hMenu, IDM_EXEC_RUN, MF_BYCOMMAND|MF_GRAYED);
      EnableMenuItem(m_hMenu, IDM_EXEC_ABORT, MF_BYCOMMAND|MF_ENABLED);
   } else {
      EnableMenuItem(m_hMenu, IDM_EXEC_RUN, MF_BYCOMMAND|MF_ENABLED);
      EnableMenuItem(m_hMenu, IDM_EXEC_ABORT, MF_BYCOMMAND|MF_GRAYED);
   }
}

void CLogoWnd::CheckDebugGoAndStepToMenus ()
{
   if (!m_bRunning || ( m_bRunning && get_debug_mode(m_exeInfo.hexe) && m_bDebugCmdAcked)) {
      EnableMenuItem(m_hMenu, IDM_DEBUG_GO, MF_BYCOMMAND|MF_ENABLED);
      m_tbDebug.SetButtonState(IDM_DEBUG_GO, TBSTATE_ENABLED);
      EnableMenuItem(m_hMenu, IDM_DEBUG_STEPTO, MF_BYCOMMAND|MF_ENABLED);
      m_tbDebug.SetButtonState(IDM_DEBUG_STEPTO, TBSTATE_ENABLED);
   } else {
      EnableMenuItem(m_hMenu, IDM_DEBUG_GO, MF_BYCOMMAND|MF_GRAYED);
      m_tbDebug.SetButtonState(IDM_DEBUG_GO, TBSTATE_INDETERMINATE);
      EnableMenuItem(m_hMenu, IDM_DEBUG_STEPTO, MF_BYCOMMAND|MF_GRAYED);
      m_tbDebug.SetButtonState(IDM_DEBUG_STEPTO, TBSTATE_INDETERMINATE);
   }
}

void CLogoWnd::CheckDebugStopMenu ()
{
   if (m_bRunning && get_debug_mode(m_exeInfo.hexe)) {
      EnableMenuItem(m_hMenu, IDM_DEBUG_STOP, MF_BYCOMMAND|MF_ENABLED);
      m_tbDebug.SetButtonState(IDM_DEBUG_STOP, TBSTATE_ENABLED);
   } else {
      EnableMenuItem(m_hMenu, IDM_DEBUG_STOP, MF_BYCOMMAND|MF_GRAYED);
      m_tbDebug.SetButtonState(IDM_DEBUG_STOP, TBSTATE_INDETERMINATE);
   }
}

void CLogoWnd::CheckOtherDebugMenus()
{
   if (m_bRunning && get_debug_mode(m_exeInfo.hexe) && m_bDebugCmdAcked) {
      EnableMenuItem(m_hMenu, IDM_DEBUG_STEPOVER, MF_BYCOMMAND|MF_ENABLED);
      m_tbDebug.SetButtonState(IDM_DEBUG_STEPOVER, TBSTATE_ENABLED);
      EnableMenuItem(m_hMenu, IDM_DEBUG_STEPOUT, MF_BYCOMMAND|MF_ENABLED);
      m_tbDebug.SetButtonState(IDM_DEBUG_STEPOUT, TBSTATE_ENABLED);
      EnableMenuItem(m_hMenu, IDM_DEBUG_STEPINTO, MF_BYCOMMAND|MF_ENABLED);
      m_tbDebug.SetButtonState(IDM_DEBUG_STEPINTO, TBSTATE_ENABLED);
   } else {
      EnableMenuItem(m_hMenu, IDM_DEBUG_STEPOVER, MF_BYCOMMAND|MF_GRAYED);
      m_tbDebug.SetButtonState(IDM_DEBUG_STEPOVER, TBSTATE_INDETERMINATE);
      EnableMenuItem(m_hMenu, IDM_DEBUG_STEPOUT, MF_BYCOMMAND|MF_GRAYED);
      m_tbDebug.SetButtonState(IDM_DEBUG_STEPOUT, TBSTATE_INDETERMINATE);
      EnableMenuItem(m_hMenu, IDM_DEBUG_STEPINTO, MF_BYCOMMAND|MF_GRAYED);
      m_tbDebug.SetButtonState(IDM_DEBUG_STEPINTO, TBSTATE_INDETERMINATE);
   }

   if (!m_bRunning || m_bRunning && get_debug_mode(m_exeInfo.hexe) && m_bDebugCmdAcked) {
      EnableMenuItem(m_hMenu, IDM_DEBUG_BREAKPOINT, MF_BYCOMMAND|MF_ENABLED);
      m_tbDebug.SetButtonState(IDM_DEBUG_BREAKPOINT, TBSTATE_ENABLED);
   } else {
      EnableMenuItem(m_hMenu, IDM_DEBUG_BREAKPOINT, MF_BYCOMMAND|MF_GRAYED);
      m_tbDebug.SetButtonState(IDM_DEBUG_BREAKPOINT, TBSTATE_INDETERMINATE);
   }
}

void CLogoWnd::EnableExecutionMenu ()
{
   CheckRunAndAbortMenus ();
   CheckDebugGoAndStepToMenus();
   CheckDebugStopMenu ();
   CheckOtherDebugMenus ();
   return;
}

LRESULT CLogoWnd::OnExecAbort(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
   SetFocus();

   if (!m_bRunning) {
      return 0;
   }
   if (get_debug_mode(m_exeInfo.hexe)) {
      debug_stop(m_exeInfo.hexe);
   }
   user_abort(m_exeInfo.hexe, true);
   return 0;
}

LRESULT CLogoWnd::OnDebugGo(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
   SetFocus();

   m_right.m_info.Enable(false);
   m_bDebugCmdAcked = false;
   if (!m_bRunning) {
      RunIt(true);
      debug_start(m_exeInfo.hexe);
   } else {
      debug_go(m_exeInfo.hexe);
   }
   EnableExecutionMenu();
   return 0;
}

LRESULT CLogoWnd::OnDebugStepTo(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
   SetFocus();

   m_right.m_info.Enable(false);
   m_bDebugCmdAcked = false;
   unsigned int targetline = m_edit.GetCurLine() + 1;

   if (!m_bRunning) {
      RunIt(true);
   }

   debug_step_to(m_exeInfo.hexe, targetline);
   EnableExecutionMenu();
   return 0;
}

LRESULT CLogoWnd::OnDebugStepOver(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
   SetFocus();

   m_right.m_info.Enable(false);
   m_bDebugCmdAcked = false;
   assert (m_bRunning);    

   debug_step_over(m_exeInfo.hexe);
   EnableExecutionMenu();
   return 0;
}

LRESULT CLogoWnd::OnDebugStepInto(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
   SetFocus();

   m_right.m_info.Enable(false);
   m_bDebugCmdAcked = false;
   assert (m_bRunning);    

   debug_step_into(m_exeInfo.hexe);
   EnableExecutionMenu();
   return 0;
}

LRESULT CLogoWnd::OnDebugToggleBreakPoint (WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
   SetFocus();

   assert (m_pBkPtMgr);

   unsigned int lineno = m_edit.GetCurLine() + 1;
   m_pBkPtMgr->ToggleBreakPoint(lineno);
   m_edit.Invalidate(FALSE);
   return 0;
}

LRESULT CLogoWnd::OnDebugStepOut(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
   SetFocus();

   m_right.m_info.Enable(false);
   m_bDebugCmdAcked = false;
   assert (m_bRunning);    

   debug_step_out(m_exeInfo.hexe);
   EnableExecutionMenu();
   return 0;
}

LRESULT CLogoWnd::OnConfig(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
   SetFocus();

   char szCurDir[_MAX_PATH];
   GetCurrentDirectory(_MAX_PATH, szCurDir);
   SetCurrentDirectory(m_szConfigDir);
   CEditConfigDlg dlg;
   if (dlg.DoModal() == IDOK) {
      m_edit.SetSpaceForTab(dlg.m_bSpaceForTab);
      m_edit.SetTabSize(dlg.m_uTabSize);
      m_edit.SetSyntaxFile(dlg.m_syntaxFilename);
   }
   SetCurrentDirectory(szCurDir);
   return 0;
}

#define MAX_RGB_LENGTH 256

LRESULT CLogoWnd::OnColorPicker(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
   SetFocus();

   CHOOSECOLOR cc;                 // common dialog box structure 
   static COLORREF acrCustClr[16]; // array of custom colors 
   static DWORD rgbCurrent = RGB(0,0,0);        // initial color selection

   // Initialize CHOOSECOLOR 
   ZeroMemory(&cc, sizeof(CHOOSECOLOR));
   cc.lStructSize = sizeof(CHOOSECOLOR);
   cc.hwndOwner = m_hWnd;
   cc.lpCustColors = (LPDWORD) acrCustClr;
   cc.rgbResult = rgbCurrent;
   cc.Flags = CC_FULLOPEN | CC_RGBINIT;

   if (ChooseColor(&cc)!=TRUE) {
      m_edit.SetFocus();
      return 0;
   }
   char szText[MAX_RGB_LENGTH];
   _snprintf (szText, MAX_RGB_LENGTH, "%d, %d, %d", 
             GetRValue(cc.rgbResult), GetGValue(cc.rgbResult), GetBValue(cc.rgbResult));
   m_edit.InsertTextToCurPos(szText);
   m_edit.SetFocus();
   return 0;
}

LRESULT CLogoWnd::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
   CFrameWnd::OnCreate(uMsg, wParam, lParam, bHandled);
   m_hIcon = LoadIcon(_Module.GetModuleInstance(), MAKEINTRESOURCE(IDI_BIG));
   SetClassLong(m_hWnd, GCL_HICON, (LONG)m_hIcon);

   RECT rc = {0, 0, 100, 22};
   rc.bottom = 22;
   m_tbFile.Create(m_reBar.m_hWnd, rc, IDR_FILETOOLBAR);
   m_tbFile.LoadToolBar(IDR_FILETOOLBAR);
   m_reBar.AddBar(m_tbFile.m_hWnd, NULL);   


   rc.right = 170;
   m_tbDebug.Create(m_reBar.m_hWnd, rc, IDR_DEBUGTOOLBAR);
   m_tbDebug.LoadToolBar(IDR_DEBUGTOOLBAR);
   m_reBar.AddBar(m_tbDebug.m_hWnd, NULL);   

   GetClientRect(&rc);
   m_splitter.SetParameters(EastWest, &m_edit, &m_right, (rc.right-rc.left)/3);
   m_splitter.CreateSplitterWnd(m_hWnd, rc);

   rc.right = 100;
   m_dlgBar.Create(m_reBar.m_hWnd);
   m_dlgBar.SetCanvasWnd(&(m_right.m_container.m_canvas));
   m_reBar.AddBar(m_dlgBar.m_hWnd, NULL);

   EnableExecutionMenu();
   m_statusBar.SetMenuPrompt(m_hMenu);
   m_hwndContent = m_splitter.m_hWnd;

   m_right.m_info.Enable(false);
   bHandled = TRUE; // Prevent CFrameWnd::OnCreate being called again.
   return 0;
}

LRESULT CLogoWnd::OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
   if (m_hMenu) {
      ::DestroyMenu(m_hMenu);
      m_hMenu = NULL;
   }
   PostQuitMessage(0);
   return 0;
}

LRESULT CLogoWnd::OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
   bHandled = TRUE;
   int ret = OnFileClose(0,0,0,bHandled);
   if (ret == IDCANCEL) {
      return 0;
   }
   DestroyWindow();
   return 0;
}

void ExecThread (void* pData)
{
   ExecInfo* info = (ExecInfo*)pData;
   CExecStub stub;
   FILE* file;
    
   save_thread_id(info->hexe);
   // System setup message queue for the thread upon the first User32 or GDI call.
   //  A dummy call to PostMessage (user32.lib).
   ::PostMessage (NULL, 0, 0, 0);

   assert (ghThreadMsgQReady);
   SetEvent(ghThreadMsgQReady);
   stub.SetExecWnd(info->hCanvas);
   file = fopen (info->pFilename, "rt");
   if (!file) {
      _TRACE (_T("Source file %s doesn't exist.\n"), info->pFilename);
      return;
   }

   set_exec_obj (info->hexe, (i_exec_t*)&stub);
   start_scanning (file);

   node_handle_t t = parse ();

   _TRACE (_T("=================================================\n"));

   if (t) {
      _TRACE(_T("R  ")); /* root node */
      print_parse_tree (t);
   } else {
      _TRACE(_T("Empty parse tree.\n"));
   }

   fclose (file);
   if (!check_syntax_error()) {
      interpret (info->hexe, t);
   } 

   return;    
}
